/*
 * FileSystemTests.cpp
 *
 *  Created on: Mar 25, 2010
 *      Author: Lukasz Spintzyk
 */

#include <iostream>
#include <iomanip>
#include "ph.h"
#include "Filesystem.h"
#include "BlockGroup.h"
#include "version.h"

#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>

#include <cppunit/TestRunner.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/extensions/TestFactoryRegistry.h>


class FileSystemTest: public CPPUNIT_NS::TestCase
{
    CPPUNIT_TEST_SUITE(FileSystemTest);
		CPPUNIT_TEST(FileSystemCreation);
		CPPUNIT_TEST(GetBlockTest);
		CPPUNIT_TEST(BitSetTablesTest);
		CPPUNIT_TEST(testERROR_FS_flag);
		CPPUNIT_TEST(InodeReadWriteTest);
    CPPUNIT_TEST_SUITE_END();

public:
    void setUp(void)
    {
        tmpnam(d_buffer);
        fclose(fopen(d_buffer, "w"));

        d_opts.blockSize=1024;
        d_opts.blocksPerGroup=d_opts.blockSize*8;
        d_opts.groupsCount = 100;
        d_opts.inodesPerGroup = 240;
        d_opts.mount.key = "AA::22::33::44::55::66::77::88::01::22::33::44::55::66::77::88";
        d_opts.mount.iv = "1122334455667788A1A2A3A4A5A6A7A8";


        d_opts.mount.file = std::string(d_buffer);
        d_fw = Filesystem::setUp(d_opts);


        BlockManager* r = d_fw->getBlockReader();

        BlockDesc* g0 = &(r->getBlockGroupDesc(0));

        memcpy(d_bufferBlock, tempTekst, strlen(tempTekst));
        r->setBlock(0,37, d_bufferBlock);
        r->setBlock(0,43, d_bufferBlock);
        r->setBlock(0,59, d_bufferBlock);
        r->setBlock(0,100, d_bufferBlock);

        g0 = &r->getBlockGroupDesc(0);
        r->setBlock(12,37, d_bufferBlock);
        r->setBlock(12,43, d_bufferBlock);
        r->setBlock(12,59, d_bufferBlock);
        r->setBlock(12,100, d_bufferBlock);


        InodeManager* ir = d_fw->getInodeReader();

        InodePtr i = Inode::newInstance();
        i->d_ctime = time(NULL);
        i->d_uid = 13;
        i->d_num = 1;
        ir->setInode(1, i);

    }
    void tearDown(void)
    {
    	d_fw.reset();
    	d_fr.reset();
    }

protected:
    void InodeReadWriteTest()
    {
        BlockManager* r = d_fw->getBlockReader();
        InodeManager* ir = d_fw->getInodeReader();

        InodePtr i = Inode::newInstance();
        i->d_ctime = time(NULL);
        i->d_uid = 17;
        i->d_num = 123;
        ir->setInode(123, i);
        d_fw.reset();


        d_fr = Filesystem::read(d_opts.mount);

        ir = d_fr->getInodeReader();
        InodePtr i2 = ir->getInode(1);

        r = d_fr->getBlockReader();
        BlockDesc& g0 = r->getBlockGroupDesc(0);
        CPPUNIT_ASSERT(i2->d_uid == 13);

        CPPUNIT_ASSERT(g0.hasInode(1));
        CPPUNIT_ASSERT(i2->d_ctime == time(NULL));


        g0.setGroupNumber(123/d_fr->getSuperBlock().d_inodesPerGroup);

        i2 = ir->getInode(123);
        //std::cout << "\n inodes per group " << d_fr->getSuperBlock().d_inodesPerGroup << std::endl;
        //std::cout << " inodes count " << d_fr->getSuperBlock().d_inodesCount << std::endl;
        //std::cout << " inodes free" << d_fr->getSuperBlock().d_freeInodes << std::endl;
        CPPUNIT_ASSERT(i2->d_uid == 17);

        CPPUNIT_ASSERT(d_fr->getSuperBlock().d_inodesPerGroup > 125);
        CPPUNIT_ASSERT(!g0.hasInode(122%d_fr->getSuperBlock().d_inodesPerGroup));
        CPPUNIT_ASSERT(g0.hasInode(123%d_fr->getSuperBlock().d_inodesPerGroup));
        CPPUNIT_ASSERT(!g0.hasInode(124%d_fr->getSuperBlock().d_inodesPerGroup));
        CPPUNIT_ASSERT(i2->d_ctime == time(NULL));

        CPPUNIT_ASSERT_EQUAL(d_fr->getSuperBlock().d_inodesCount,d_fr->getSuperBlock().d_freeInodes + 2 );


    }

    void FileSystemCreation(void)
    {
    	assert( sizeof(Inode) == 128 );
    	createFilesystem(1024);
    	createFilesystem(1024<<2);
    	createFilesystem(1024<<3);
    	createFilesystem(1024<<4);
    }

    void GetBlockTest(void)
    {
        //write some groups.
        BlockManager* r = d_fw->getBlockReader();
        BlockDesc& gnew = r->getBlockGroupDesc(0);
      	if (gnew.hasBlock(2304))
        		std::cout << "\n inew " << 2304 << std::endl;

        byte* buffer2 = new byte[sizeof(d_bufferBlock)];
        for (uint32_t i = 0; i < sizeof(d_bufferBlock); ++i)
        {
        	d_bufferBlock[i]=i;
        }
        uint32_t blockSize = d_fw->getSuperBlock().d_blockSize;

        r->setBlock(0, 37, d_bufferBlock + blockSize* 0);
        r->setBlock(0, 38, d_bufferBlock + blockSize* 1);
        r->setBlock(0, 39, d_bufferBlock + blockSize* 2);
        r->setBlock(0, 40, d_bufferBlock + blockSize* 3);
        r->setBlock(0, 41, d_bufferBlock + blockSize* 4);

        r->setBlock(5, 123, d_bufferBlock + blockSize* 5);



        //close filesystem
         d_fw.reset();

        d_fr = Filesystem::read(d_opts.mount);

        r = d_fr->getBlockReader();

        uint32_t block_size = d_fr->getSuperBlock().d_blockSize;
        BlockDesc& g0 = r->getBlockGroupDesc(0);
      	if (g0.hasBlock(2304))
        		std::cout << " i " << 2304 << std::endl;

        r->getBlock(0, 37, buffer2 + block_size* 0);
        r->getBlock(0, 38, buffer2 + block_size* 1);
        r->getBlock(0, 39, buffer2 + block_size* 2);
        r->getBlock(0, 40, buffer2 + block_size* 3);
        r->getBlock(0, 41, buffer2 + block_size* 4);
        r->getBlock(0, 42, buffer2 + block_size* 5);

        CPPUNIT_ASSERT( memcmp(buffer2, d_bufferBlock, 5*block_size) == 0 );
        CPPUNIT_ASSERT( memcpy(buffer2+6*block_size
							, d_bufferBlock+6*block_size, block_size) != 0 );

        r->getBlock(5, 123, buffer2 + block_size* 5);
        CPPUNIT_ASSERT( memcmp(buffer2+ block_size* 5,
        		d_bufferBlock+ block_size* 5, block_size) == 0 );



        r->readSuperBlock();
        SuperBlock& sb = d_fr->getSuperBlock();
        uint32_t blocks = sb.d_blocksPerGroup;
        CPPUNIT_ASSERT(g0.hasBlock(0) && g0.hasBlock(1) && g0.hasBlock(2) && g0.hasBlock(3) && g0.hasBlock(4));
        CPPUNIT_ASSERT(g0.hasBlock(37) && g0.hasBlock(38) && g0.hasBlock(39) && g0.hasBlock(40) && g0.hasBlock(41));


        uint32_t inode_blocks_nb = (sb.d_inodesPerGroup*Inode::size())/sb.d_blockSize;

    	for(uint32_t i = 0; i < inode_blocks_nb+g0.sizeInBlocks() ; ++i)
    	{
    		CPPUNIT_ASSERT(g0.hasBlock(i));
    	}

        for (int i = 42; i < 43; ++i)
        {
        	CPPUNIT_ASSERT(!g0.hasBlock(i));
        }

        for (int i = 44; i < 59; ++i)
		{
			CPPUNIT_ASSERT(!g0.hasBlock(i));
		}

        for (int i = 60; i < 100; ++i)
		{
			CPPUNIT_ASSERT(!g0.hasBlock(i));
		}
        for (uint32_t i = 101; i < blocks; ++i)
		{
        	if (g0.hasBlock(i))
        		std::cout << " i " << i << std::endl;

			CPPUNIT_ASSERT(!g0.hasBlock(i));
		}
        delete[] buffer2;


    }

    void testERROR_FS_flag(void)
    {
    	// filesystem is not unmounted so d_state is EXT2_ERROR_FS
        //d_fw.reset();

        d_fr = Filesystem::read(d_opts.mount);
        SuperBlock& sb = d_fr->getSuperBlock();
        CPPUNIT_ASSERT(sb.d_state == EXT2_ERROR_FS);
    }

    void BitSetTablesTest(void)
    {
    	return;
        boost::shared_ptr<Filesystem> fnew = d_fw;

        //write some groups.
        BlockManager* r = fnew->getBlockReader();



        fnew.reset();

        fnew = Filesystem::read(d_opts.mount);
        r = fnew->getBlockReader();
        BlockDesc& g0 = r->getBlockGroupDesc(0);
        CPPUNIT_ASSERT(g0.hasBlock(0) && g0.hasBlock(1) && g0.hasBlock(2) && g0.hasBlock(3));

        CPPUNIT_ASSERT(g0.hasBlock(37) && g0.hasBlock(43) && g0.hasBlock(59) && g0.hasBlock(100));

        memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
        r->getBlock(0,37, d_bufferBlock);
        CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
		r->getBlock(0,43, d_bufferBlock);
		CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
		r->getBlock(0,59, d_bufferBlock);
		CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
		r->getBlock(0,100, d_bufferBlock);
		CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		SuperBlock& gsb = g0.getSuperBock();
		SuperBlock& ssb = fnew->getSuperBlock();
		CPPUNIT_ASSERT( ssb == gsb );




        BlockDesc& g12 = r->getBlockGroupDesc(12);

        gsb = g12.getSuperBock();
        ssb = fnew->getSuperBlock();
        CPPUNIT_ASSERT( ssb == gsb );

        CPPUNIT_ASSERT(g12.hasBlock(0) && g12.hasBlock(1) && g12.hasBlock(2) && g12.hasBlock(3));

        CPPUNIT_ASSERT(g12.hasBlock(37) && g12.hasBlock(43) && g12.hasBlock(59) && g12.hasBlock(100));

        memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
	    r->getBlock(12,37, d_bufferBlock);
	    CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
		r->getBlock(12,43, d_bufferBlock);
		CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
		r->getBlock(12,59, d_bufferBlock);
		CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);

		memset(d_bufferBlock, 0, sizeof(d_bufferBlock));
		r->getBlock(12,100, d_bufferBlock);
		CPPUNIT_ASSERT(memcmp(d_bufferBlock, tempTekst, strlen(tempTekst)) == 0);
    }

    void createFilesystem(uint32_t blockSize)
    {

    	tmpnam(d_buffer);
    	fclose(fopen(d_buffer, "w"));
    	FilesystemMkfsArgs opts;
        opts.blockSize=blockSize;
        opts.blocksPerGroup = opts.blockSize*8;
        opts.groupsCount = 100;
        opts.blocksPerGroup=245;
        opts.inodesPerGroup = (blockSize/Inode::size())*10;
        opts.mount.key = "AA::22::33::44::55::66::77::88::01::22::33::44::55::66::77::88";
        opts.mount.iv = "1122334455667788A1A2A3A4A5A6A7A8";


        opts.mount.file = std::string(d_buffer);

        boost::shared_ptr<Filesystem> f1= Filesystem::setUp(opts);
        f1.reset();
        boost::shared_ptr<Filesystem> f = Filesystem::read(opts.mount);

        CPPUNIT_ASSERT(opts.blockSize == f->getSuperBlock().d_blockSize);
        CPPUNIT_ASSERT(opts.blocksPerGroup == f->getSuperBlock().d_blocksPerGroup);
        CPPUNIT_ASSERT(opts.inodesPerGroup == f->getSuperBlock().d_inodesPerGroup);
        CPPUNIT_ASSERT(f->getSuperBlock().d_magicNumber == MAGIC_NUMBER);
        CPPUNIT_ASSERT(f->getSuperBlock().d_revisionNumber == REVISION_NUMEBER);

        SuperBlock& sb = f->getSuperBlock();

        CPPUNIT_ASSERT( sb.d_CreatorOs == EXT2_OS_LINUX );

        CPPUNIT_ASSERT( sb.d_defResUID == EXT2_DEF_RESUID );
        CPPUNIT_ASSERT( sb.d_defResGID == EXT2_DEF_RESGID );

        CPPUNIT_ASSERT( sb.d_errors == EXT2_ERRORS_CONTINUE );
        CPPUNIT_ASSERT( sb.d_firstDataBlock == (sb.d_blockSize == 1024 ? 1 : 0) );
        CPPUNIT_ASSERT( sb.d_blocksCount/sb.d_blocksPerGroup == sb.d_groupNum);


        BlockManager* r = f->getBlockReader();
        //std::cout << "inodes " << sb.d_inodesPerGroup << std::endl;
        BlockDesc& g = r->getBlockGroupDesc(0);

        //std::cout << " Block COunt " << sb.d_blocksCount << std::endl;
         //std::cout << " Free Block COunt " << sb.d_freeBlocks<< std::endl;
         //std::cout << " Group number" << sb.d_groupNum<< std::endl;
         //std::cout << " InodeTableSize" << g.inodeTableSize() << std::endl;

        CPPUNIT_ASSERT( sb.d_freeBlocks ==
        		sb.d_blocksCount - sb.d_groupNum*(g.sizeInBlocks() + g.inodeTableSize())) ;// TODO z- check



        CPPUNIT_ASSERT( sb.d_inodesCount == sb.d_groupNum * sb.d_inodesPerGroup );
        CPPUNIT_ASSERT( sb.d_freeInodes == sb.d_inodesCount );

        CPPUNIT_ASSERT_DOUBLES_EQUAL( sb.d_lastCheck, time(NULL), 5 );
        CPPUNIT_ASSERT( sb.d_checkInterval == sb.d_checkInterval );
        CPPUNIT_ASSERT( sb.d_logBlockSize == sb.d_blockSize/1024 );
        CPPUNIT_ASSERT( sb.d_maxMntCount == sb.d_maxMntCount );
        CPPUNIT_ASSERT( sb.d_mntCount == 0 );
        CPPUNIT_ASSERT( sb.d_mtime == sb.d_lastCheck );
        CPPUNIT_ASSERT( sb.d_state == EXT2_VALID_FS );

        CPPUNIT_ASSERT_DOUBLES_EQUAL( sb.d_wtime, time(NULL), 5 );




    }

private:


    void setTestBlock(byte* block, int w, int h, int seed=1)
    {
        int k = seed;
        for (int i = 0; i < w; ++i)
        {
            for (int j = 0; j < h; ++j)
            {
                block[i * h + j] = k++;
            }
        }
    }

    void printBlock(byte* block, int w, int h)
    {
        for (int j = 0; j < h; ++j)
        {
            for (int i = 0; i < w; ++i)
            {
                std::cout << std::hex << std::setw(4) << (unsigned int) block[i * h + j]
                        << " ";
            }
            std::cout << std::endl;
        }
    }


    char d_buffer[L_tmpnam];
    char d_bufferBlock[8*1024*10];
    boost::shared_ptr<Filesystem> d_fw;
    boost::shared_ptr<Filesystem> d_fr;
    FilesystemMkfsArgs d_opts;
    static const char* tempTekst;

};

const char* FileSystemTest::tempTekst = "Ala ma kota kot ma ale";

CPPUNIT_TEST_SUITE_REGISTRATION(FileSystemTest);

int main(int ac, char **av)
{
    //--- Create the event manager and test controller
    CPPUNIT_NS::TestResult controller;
    //--- Add a listener that colllects test result
    CPPUNIT_NS::TestResultCollector result;
    controller.addListener(&result);
    //--- Add a listener that print dots as test run.
    CPPUNIT_NS::BriefTestProgressListener progress;
    controller.addListener(&progress);
    //--- Add the top suite to the test runner
    CPPUNIT_NS::TestRunner runner;
    runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
    runner.run(controller);

    return result.wasSuccessful() ? 0 : 1;
}
